#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <string.h>
#include <errno.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <libgen.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/select.h>
#include <time.h>


#define	BACK_LOG	13
#define	MAX_FD		1024
#define	ARR_SIZE(x)	( sizeof(x) / sizeof(x[0]) ) //calculate array size
#define	BUF_SIZE	1024


int StartSocket(int *server_fd, char *listen_addr, int server_port) ;
void print_usage(const char *program_name); 
char cover(char ch) ;

int main(int argc, char **argv)
{
	int			listen_fd ;
	char			*program_name ;
	int			opt = -1 ;
        int			port = 0 ;
	int			daemon_run = 0 ;
	fd_set			readset ;
	int			i, j ;
	int			max_fd = 0 ;
	int			fd_arr[MAX_FD] ;
	int	 		conn_flags ;
	int			conn_fd ;
	struct sockaddr_in	conn_addr ;
	socklen_t		conn_addr_len ;
	char			buf[BUF_SIZE] ;
	int			reval ;
	char			wbuf[BUF_SIZE] = "Hello client, Welcome to connect,If you input get_time will get server time ,else will echo upper buf" ;
	time_t			ticks ;

	program_name = basename(argv[0]) ;
	const char *short_opts = "p:hd";  
	const struct option long_opts[] =
	{
		{"help", no_argument, NULL, 'h'},  
		{"port", required_argument, NULL, 'p'},  
		{"daemon", no_argument,NULL, 'd'} ,
		{0, 0, 0, 0}  
	};  
	
	while ((opt= getopt_long(argc, argv, short_opts, long_opts,NULL)) != -1) 
	{
		switch (opt) 
		{
			case 'd':
				daemon_run = 1 ;
				break ;

			case 'p':
				port = atoi(optarg) ;
				break ;

			case 'h':
				print_usage(program_name) ;
				return 0;
		}
	}

	if( !port )
	{
		print_usage(program_name);
		return 0;
	}

	if(daemon_run == 1)
	{
		printf("the process will running in backgrund\n") ;
		daemon(0,0) ;
	}



	program_name = basename(argv[0]) ;

	if( StartSocket(&listen_fd, NULL, port) < 0 ) 
	{
		printf("StartSocket() error: %s\n", strerror(errno) ) ;
		return -1 ;
	}
	printf("StartSocket() sucessful, listen[%d]\n", listen_fd) ;


	memset(fd_arr, 0, sizeof(fd_arr) ) ;
	fd_arr[0] = listen_fd ;
	while(1)
	{
		FD_ZERO(&readset) ;
		max_fd = 0 ;

		for(i = 0; i < ARR_SIZE(fd_arr); i++)
		{
			if(fd_arr[i] != 0)
			{
				FD_SET(fd_arr[i], &readset) ;
				max_fd = fd_arr[i] > max_fd ? fd_arr[i]:max_fd ;
			}

		}
		printf("\n/***********************************/\n") ;
		printf("               max_fd:%d\n", max_fd) ;

		/*	Program will obstruct in here	*/
		reval = select(max_fd+1, &readset,NULL,NULL,NULL ) ;
		if( reval < 0 )
		{
			printf("Select() failed: %s\n", strerror(errno)) ;
			goto cleanup ;
		}
		if( reval == 0 )
		{
			printf("Select() time out!\n") ;
			goto cleanup ;
		}
		/*	Select found event come		*/
		printf("Have event come!\n") ;
		if( FD_ISSET(fd_arr[0], &readset) ) //listen_fd have event, new client want to connect
		{	

			conn_flags = 0 ;
			conn_fd = accept(listen_fd, (struct sockaddr*) &conn_addr,  &conn_addr_len) ;
			if(conn_fd < 0)
			{
				printf("Accept() failed:%s\n", strerror(errno)) ;
				continue;
			}

			for(i=0;i<ARR_SIZE(fd_arr);i++)
			{
				if( fd_arr[i] == 0 )  
				{	
					printf("accept client_fd[%d] [%s:%d] sucessful!\n", conn_fd, inet_ntoa(conn_addr.sin_addr), ntohs(conn_addr.sin_port) ) ;
					if (write( conn_fd, wbuf, strlen(wbuf) ) < 0 )
		       			{
						printf("write to client[%d] failure: %s and close conn_fd!\n", conn_fd, strerror(errno) ) ;
						break ;				//jump out of the loop, conn_flags == 0
					}
					conn_flags = 1;
					fd_arr[i] = conn_fd ;
					break ;	
				}
			}

			if(conn_flags == 0)
			{
				printf("accept client_fd[%d] [%s:%d] sucessful, but fd_array is full or write to conn_fd failed!\n", conn_fd, 
						inet_ntoa(conn_addr.sin_addr), ntohs(conn_addr.sin_port) ) ;
				close(conn_fd) ;
			}
			
		}
			
		else // Already connect_fd have event
		{
			
			for(i=0; i<ARR_SIZE(fd_arr); i++)
			{
				if( FD_ISSET(fd_arr[i], &readset) )
				{
					memset(buf, 0, sizeof(buf) ) ;
					reval = read(fd_arr[i], buf, sizeof(buf)) ;
					printf("read %d byte from client[%d]: %s\n", reval ,fd_arr[i] ,buf) ;
						
					if( reval < 0 )
					{
						printf("Read() error: %s, and close conn_fd[%d]\n", strerror(errno), fd_arr[i]) ;
						close(fd_arr[i]) ;
						fd_arr[i] = 0 ;
						break ;
					}
					if( reval == 0 )
					{
						printf("client disconnect!\n") ;
						close(fd_arr[i]) ;
						fd_arr[i] = 0 ;
						break ;
					}
					else		//read return value > 0
					{
						/*	client send get_time write time to client	*/
						if(strcmp(buf,"get_time")==0)	
						{
							ticks = time(NULL) ;
							snprintf( buf, sizeof(buf), "%.24s",ctime(&ticks)) ;
							if(write( fd_arr[i], buf, strlen(buf)) <0 )
							{	
								printf("write to client[%d] failure: %s and thread will exit!\n", fd_arr[i], strerror(errno)) ;
								close(fd_arr[i]) ;
								fd_arr[i] = 0 ;													
							}	
						}
						/*	client send other echo upper buf to client	*/
						else
						{
							for(j = 0; j < reval; j++ )
							{
							buf[j] = cover(buf[j]) ;		
							}
							if( write( fd_arr[i], buf, reval) < 0 )
							{
								printf("Write error:%s, and close conn_fd[%d]\n", strerror(errno), fd_arr[i])  ;
								close(fd_arr[i]) ;
								fd_arr[i] = 0 ;
							}
						}	
					}
				}
							
			}
		}	
	}
	



cleanup:
	close(listen_fd) ;
	
	return 0 ;
}

char cover(char ch)
{
	if(ch>'z'||ch<'a')
	{
		return ch ;
	}
	else
		return (ch - ('a' - 'A') ); 
}

void print_usage(const char *program_name) 
{
	printf("\n%s -- (2018.12.2)\n", program_name);  
	printf(" Usage: %s -p <server_port> [-d]  [-h <server_use_help>]\n", program_name);  
	printf("        -p --port       the server listen port\n") ;
	printf("        -h --help       the server file how to use\n");  
	printf("        -d --daemon     the server running in backgrund\n");  
	return ;
}  


int StartSocket(int *server_fd ,char *listen_addr, int server_port)
{	
	int			fd ;
	struct sockaddr_in	serv_addr ;	
	int			reval ;
	
	if(server_port<1024)
	{
		printf("Set error server_port: Setting the well-known port number!\n") ;
		reval =  -1;
		goto cleanup ;
	}
	
	fd = socket( AF_INET, SOCK_STREAM, 0) ;
	if( fd < 0)
	{
		printf("socket() failed: %s\n", strerror(errno)) ;
		reval = -2 ;
		goto cleanup ;
	}

	memset( &serv_addr, 0 ,sizeof(serv_addr) ) ;
	serv_addr.sin_family = AF_INET ;
	if( !listen_addr )             
	{
		serv_addr.sin_addr.s_addr = htonl(INADDR_ANY) ;		
	}
	else //If addr have value
	{
		serv_addr.sin_addr.s_addr = inet_aton(listen_addr, &serv_addr.sin_addr) ;
	}
	serv_addr.sin_port = htons( server_port) ;
	
	int reuse = 1 ;
	setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)); //reuseaddr

	if( bind( fd, (struct sockaddr *)&serv_addr, sizeof(serv_addr) ) < 0)
	{
		printf("Bind() failed: %s\n", strerror(errno)) ;
		reval = -3 ;
		goto cleanup ;
	}

	if( listen( fd,BACK_LOG) < 0)
	{
		printf("Listen() error: %s\n", strerror(errno)) ;
		reval = -4 ;
		goto cleanup ;
	}
	
cleanup:
	if(reval < 0)
	{	
		close(fd) ;
		return -1 ;
	}
	else
		*server_fd = fd ;

	return 0 ;

}
